Topics Covered

  1. Visualizing data using ggplot2
  2. Data Joins

Visualizing data using ggplot2

ggplot2 is a declarative plotting library in R. The gg in ggplot2 stands for “Grammar of Graphics” a book on the principles of data visualization that the package is based on. According to this article:

a grammar of graphics is a framework which follows a layered approach to describe and construct visualizations or graphics in a structured manner. A visualization involving multi-dimensional data often has multiple components or aspects, and leveraging this layered grammar of graphics helps us describe and understand each component involved in visualization — in terms of data, aesthetics, scale, objects and so on.

You can think about the layering of graphics in the same way as composition that you learnt last week. The idea of building complex functionality by breaking things down into smaller pieces using pipes.

Source: A Comprehensive Guide to the Grammar of Graphics for Effective Visualization of Multi-dimensional Data - Dipanjan Sarkar

There are seven layers in ggplot. All plots need atleast three out of these seven layers (the rest adopt default values if not specified). These are as follows:

  1. A data layer
  2. An aesthetics layer
  3. A geom or statistics layer

Lets start plotting!

Basic charts

In this section we will explore a few basic chart types. Lets also open the reference website for ggplot in a new tab so that we have it handy. We will be using the gapminder dataset to illustrate ggplot commands. This dataset is available as a package of the same name 1. Go ahead and install this on your computer. The gapminder dataset is displayed below. It provides data on life expectancy, population and GDP per capita for each country for five year intervals from 1952 to 2007.

rm(list = ls())
##load the packages
library(tidyverse); library(nycflights13); library(gapminder)
gapminder

Scatterplot

The first plot we will explore is the scatter-plot. A scatter-plot simply places a point on a cartesian (2-D) coordinate system for corresponding values of the x and y variables. Lets explore the relationship between year and life expectancy using a scatter-plot.

Go through the code below to identify the data, aesthetics and geom layers. As can be seen below, this is not a particularly useful chart. Each year in the data has several observations for life expectancy (one for each country), this results in a chart for which it is difficult to perceive a clear trend. In the next step we will see how to improve this chart.

##specify the data and aesthetics layer
ggplot(data = gapminder, aes(x = year, y = lifeExp)) +
    ##specifying the geom type
    geom_point() +
    ##adding x and y axis titles
    labs(x = "Year", y = "Life Expectancy")

Ninja Tasks

  1. Use a scatter-plot to explore the relationship between GDP per capita and life expectancy
  2. Bonus question: Can you map the population to a particular aesthetic so that it can be displayed on the chart too?

🏆Solution🏆

1. GDP per capita vs life expectancy

The gapminder data consists of observations for each country for each year. We are interested in capturing the relationship between GDP per capita and the life expectancy. However, since the data is a time series, we need to average the GDP per capita and life expectancy for each country for the entire time series before plotting to smooth out time trends and avoid over-plotting 2. This would allow us to observe the relationship between the two variables more clearly without any confusing time trends 3.

gapminder %>% 
    ##find the mean gdp per capita and life expectancy for each country
    group_by(country) %>% 
    summarise(meanGDPperCap = mean(gdpPercap, na.rm = T), 
              meanLifeExp = mean(lifeExp, na.rm = T)) %>% 
    ##specify the data and aesthetics layer
    ggplot(., aes(x = meanGDPperCap, y = meanLifeExp)) + 
    ##specify the geom
    geom_point(colour = "blue", size = 2.5, alpha = 0.8) +
    ##specify titles
    labs(x = "Mean GDP per capita", 
         y = "Mean Life Expectancy", 
         title = "Life Expectancy increases with GDP per capita")

The scatter-plot shows one single point for each country in the dataset representing the mean life expectancy and GDP per capita. Make sure to read the code used to generate the chart carefully so that you understand it fully. Why are the colour and size paramters in the geom_point() not inside an aes()?

3. Map an additional aesthetic to the plot

Lets map the size of each point to the meanPop variable. I also introduce an additional aesthetic mapping of colour to the continent variable. In addition, since there are large values in the GDP per capita, we can plot the log of the mean GDP per capita to smooth out the large numbers (watch the video below if you don’t fully understand logarithmic scales).

We can do this in two ways, first we could mutate meanGDPperCap by taking its log and plotting that (try this yourself)4, and the second option would be to use ggplots inbuilt scale_x_log10 command to transform the plotting scale instead of the variable. While the charts would largely look the same, the second option preserves the actual variable and plots it on the new scale. Plot the first option on your own to see if you can spot the difference between the plot below and that one.

The application of the log scale has the effect of making it appear as if the relationship between GDP per capita and life expectancy is linear. This is not true, since as we observed in the previous chart the true relationship between these two variables is parabolic. As GDP increases, the corresponding increase in life expectancy gets lower over time (probably because we can’t extend human lifespans beyond a certain limit, no matter how hard we try). This is one reason, why we should be extremely careful with scale transformations. We should only use them if we have a clear reason for why they are necessary. And when we do choose to use them we should have a clear sense for what they are doing to the interpretation of the chart.

gapminder %>% 
    ##Calculate country level information
    group_by(country) %>% 
    summarise(meanGDPperCap = mean(gdpPercap, na.rm = T), 
              meanLifeExp = mean(lifeExp, na.rm = T), 
              meanPop = mean(pop, na.rm = T),
              continent = unique(continent)) %>% 
    ##specify data and aesthetics
    ggplot(., aes(x = meanGDPperCap, 
                  y = meanLifeExp, 
                  size = meanPop, 
                  colour = continent)) + 
    ##specify the geom
    geom_point(alpha = 0.6) +
    ##apply scale transformation
    scale_x_log10() +
    ##specify the titles
    labs(x = "Mean GDP per capita", 
         y = "Mean Life Expectancy", 
         title = "Life Expectancy increases exth GDP per capita", 
         size = "Mean population",
         colour = "Continent") +
    ##adjust the position and layout of the legends
    theme(legend.position = "top",
          legend.box = "vertical",
          legend.spacing.y = unit(-8, "pt"),
          )

Line chart

Now lets use dplyr to calculate the average global life expectancy by year and plot that using a line. As can be seen below, the average global life expectancy has been increasing over the years.

gapminder %>% 
    ##group by year to calculate the yearly average life expectancy
    group_by(year) %>% 
    summarise(meanLifeExp = mean(lifeExp, na.rm = T)) %>% 
    ##draw a line marking the trend of life expectancy over years
    ggplot(., aes(x = year, y = meanLifeExp)) +
    geom_line() +
    labs(title = "Average life expectancy has been increasing", x = "Years", y = "Mean Life Expectancy")

Nw lets go a bit further and explore the trend for life expectancy for different countries. No

ggplot(data = gapminder, mapping = aes(x = year, y = lifeExp)) +
    ##colour and group are mapped to continent and country and aesthetic is set to 0.5
    geom_line(mapping = aes(colour = continent, group = country), alpha = 0.5) +
    labs(title = "Countries in Africa have lower life expectancy", x = "Year", y = "Life Expectancy") +
    theme(
        legend.position = "top"
        
    )

In the chart above there are a few countries were there are sudden drops in life expectancy. These are because of genocides that occurred in Rwanda, Cambodia and China. The chart below used color and alpha mapping to highlight these values. Notice how, I use a alphaMapping and colorMapping variables to fix the alpha and colour aesthetics in the chart.[^6]

##repeat the same but with one line for each continent/country, (guess why there is a sudden drop for a few countries)?
alphaMapping <- if_else(gapminder$country %in% c("Rwanda", "Cambodia", "China"), 0.8, 0.1)
colorMapping <- if_else(gapminder$country %in% c("Rwanda", "Cambodia", "China"), "darkred", "black")
ggplot(data = gapminder, mapping = aes(x = year, y = lifeExp, group = country)) +
    geom_line(alpha = alphaMapping, colour = colorMapping) +
    labs(title = "The impact of genocides on life expectancy", x = "Year", y = "Life Expectancy")

Ninja Tasks

  1. Draw a line chart showing the trend for average GDP per capita through time for each continent

🏆Solution🏆 The chart below shows that poorer countries in the Africa and Asia are still lagging behind those in the West. According to Wikipedia convergence can be described as follows:

The idea of convergence in economics (also sometimes known as the catch-up effect) is the hypothesis that poorer economies’ per capita incomes will tend to grow at faster rates than richer economies. As a result, all economies should eventually converge in terms of per capita income.

gapminder %>% 
    group_by(continent, year) %>% 
    summarise(meanGDP = mean(gdpPercap, na.rm = T)) %>% 
    ggplot(., aes(x = year, y = meanGDP)) +
    geom_line(aes(colour = continent)) +
    labs(x = "Year", y = "Mean GDP per Capita", title = "Convergence where art thou?", colour = "Continent") +
    theme(legend.position = "top")

  1. Histogram Watch the video below to refresh your understanding of histograms. The plot below shows the histogram of gdpPerCap in the gapminder dataset. There is however a problem with this chart. It shows the distribution of GDP per capita over time. We are however not interested in the spread of GDP per capita across time, rather our interest is in seeing the spread across countries. In this case, we might be better off by filtering down to a single year and observing the distribution of GDP per capita across countries. Lets try this out with the training exercise.
ggplot(data = gapminder, aes(gdpPercap)) +
    geom_histogram(bins = 30)

Ninja Tasks

  1. Draw a histogram to show the characteristics of population

🏆Solution🏆

For this chart we select the latest year in the dataset and plot the distribution of the population. Could you think of the pros and cons for using this method versus the one in which we calculate the average population for each country over the entire dataset and plotting that?

gapminder %>% 
    filter(year == max(year, na.rm = T)) %>% 
    ggplot(., aes(pop)) +
    geom_histogram(bins = 60) +
    labs(x = "Population", y = "Count")

Bar chart

The chart below shows the average life expectancy for different continents for the most recent year in the data. Can you think of the reason why it might be slightly better to have only considered the most recent year when calculating the average life expectancy for a continent? Also notice, how the bars are aligned from the smallest to the tallest. Can you find out how I might have achieved this?

##draw a bar chart with average life expectancy in different continents
gapminder %>% 
    filter(year == max(year, na.rm = T)) %>% 
    group_by(continent) %>% 
    summarise(meanLifeExp = mean(lifeExp, na.rm = T)) %>% 
    ggplot(., aes(x = reorder(continent, meanLifeExp), y = meanLifeExp)) +
    geom_col() +
    labs(y = "Mean life expectancy") +
    theme(
        axis.title.x = element_blank()
    )

NA

Ninja Tasks

  1. Draw a bar chart showing the average lifeExp across years

🏆Solution🏆

gapminder %>% 
    group_by(year) %>% 
    summarise(meanLifeExp = mean(lifeExp, na.rm = T)) %>% 
    ggplot(data = ., aes(x = year, y = meanLifeExp)) +
    geom_col()

NA
gapminder %>% 
    group_by(year) %>% 
    summarise(meanGDP = mean(gdpPercap, na.rm = T)) %>% 
    ggplot(., aes(x = year, y = meanGDP)) +
    geom_col()

Facetting

##reuse some of the plots above as a facetted plot
ggplot(gapminder, aes(x = year, y = lifeExp)) +

Ninja Tasks

  1. Show the relationship between GDP per capita and Life Expectancy using a faceted chart of your choice

Layering multiple geoms

##draw a line of fit in a chart of population and life expectancy
gapminder %>% 
    group_by(country) %>% 
    summarise(continent = unique(continent), meanPop = mean(pop, na.rm = T), meanLifeExp = mean(lifeExp, na.rm = T)) %>% 
    filter(meanPop < 5e7) %>% 
ggplot(., aes(x = meanPop, y = meanLifeExp)) +
    geom_jitter() + 
    geom_smooth(method = "lm", se = F)

Ninja Tasks

  1. Add a line of best fit to a scatter plot showing the relationship between GDP per capita and life expectancy

Adding summary stats to plots

##explore gdp per capita over the years using a jitter plot and stat_summary (rule of thumb, always stay as close to the data as possible)
    
ggplot(gapminder, aes(x = year, y = gdpPercap)) +
    geom_jitter()

##Using vline and hline

Ninja Tasks

  1. Add a vertical line showing the mean of population to the histogram showing its distribution

Add some style

##Use the plot from the previous section and add some pizazz (explore ggthemes, legend position etc)

Add some more complexity (based on time)

Coords

Scales

Data Joins

There are 6 different types of joins. These are as follows:

  1. inner_join(): return all rows from x where there are matching values in y, and all columns from x and y. If there are multiple matches between x and y, all combination of the matches are returned
  2. left_join(): return all rows from x, and all columns from x and y. Rows in x with no match in y will have NA values in the new columns. If there are multiple matches between x and y, all combinations of the matches are returned.
  3. right_join(): return all rows from y, and all columns from x and y. Rows in y with no match in x will have NA values in the new columns. If there are multiple matches between x and y, all combinations of the matches are returned.
  4. full_join(): return all rows and all columns from both x and y. Where there are not matching values, returns NA for the one missing.
  5. semi_join(): return all rows from x where there are matching values in y, keeping just columns from x. A semi join differs from an inner join because an inner join will return one row of x for each matching row of y, where a semi join will never duplicate rows of x.
  6. anti_join(): return all rows from x where there are not matching values in y, keeping just columns from x
band_members
band_instruments
inner_join(band_members, band_instruments)
Joining, by = "name"
left_join(band_members, band_instruments)
Joining, by = "name"
right_join(band_members, band_instruments)
Joining, by = "name"
full_join(band_members, band_instruments)
Joining, by = "name"
semi_join(band_members, band_instruments)
Joining, by = "name"
anti_join(band_members, band_instruments)
Joining, by = "name"

Left Join

This is the most popular type of join (analogous to Vlookup in Excel).

library(nycflights13)
flightsWithWeather <- left_join(flights, weather, by = c("origin", "month", "day", "hour"))
weather

  1. The gapminder package is an excerpt of the data that exists here. This data was created by the Gapminder Foundation led by Hans Rosling.

  2. Over-plotting happens when data points are plotted on top of each other multiple times. It makes charts more confusing and difficult to read. It should be avoided as far as possible

  3. We could also do this by filtering out the most recent year to study the relationship. Both are valid options, however the former has the benefit of capturing more information since it takes into account the entire time series of data that is available to calculate the mean values, while the latter disregards all but one year of the data. In this case this suits our purposes of wanting to study the relationship between GDP per capita and life expectancy.

  4. You can do this using the following command ggplot(., aes(x = log(meanGDPperCap, base = 10), y = meanLifeExp)) instead of ggplot(., aes(x = meanGDPperCap, y = meanLifeExp)). Can you tell the difference between this plot and the one that was made using the scale transformation?

LS0tCnRpdGxlOiAiTnVtYmVycyBOaW5qYSBXZWVrIDI6IERhdGEgdmlzdWFsaXphdGlvbnMgYW5kIGpvaW5zIgphdXRob3I6IHwKICB8IEhhcmkgU3ViaGFzaAogIHwgRGF0YSBTY2llbnRpc3QgQE5SR0kKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGhpZ2hsaWdodDoga2F0ZQogICAgc21hcnQ6IHllcwogICAgdGhlbWU6IGNvc21vCiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogICAgaW5jbHVkZXM6IGFzc2V0cy9oZWFkZXIuaHRtbApjc3M6IGFzc2V0cy9jdXN0b20uY3NzCi0tLQo8ZGl2IHN0eWxlPSAiZmxvYXQ6cmlnaHQ7IHBvc2l0aW9uOiByZWxhdGl2ZTsgdG9wOiAtODBweDsgcGFkZGluZy1sZWZ0OiAwcHgiPgpgYGB7ciwgZWNobz1GQUxTRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgb3V0LndpZHRoPScxMCUnfQojI2FkZCB0aGUgaWNvbgprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiYXNzZXRzL2ltYWdlcy9uaW5qYS1sb2dvMi5qcGciKQpgYGAKPC9kaXY+CgojI1RvcGljcyBDb3ZlcmVkCgoxLiBWaXN1YWxpemluZyBkYXRhIHVzaW5nIGdncGxvdDIKMi4gRGF0YSBKb2lucwoKPGRpdiBjbGFzcz0iZnVsbC13aWR0aCI+CgpgYGB7ciwgZWNobz1GQUxTRX0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImFzc2V0cy9pbWFnZXMvaWFuLWRvb2xleS00MDc4NDYtdW5zcGxhc2guanBnIikKYGBgCgo8L2Rpdj4KCiMjVmlzdWFsaXppbmcgZGF0YSB1c2luZyBnZ3Bsb3QyCmdncGxvdDIgaXMgYSBkZWNsYXJhdGl2ZSBwbG90dGluZyBsaWJyYXJ5IGluIFIuIFRoZSBnZyBpbiBnZ3Bsb3QyIHN0YW5kcyBmb3IgIkdyYW1tYXIgb2YgR3JhcGhpY3MiIGEgYm9vayBvbiB0aGUgcHJpbmNpcGxlcyBvZiBkYXRhIHZpc3VhbGl6YXRpb24gdGhhdCB0aGUgcGFja2FnZSBpcyBiYXNlZCBvbi4gQWNjb3JkaW5nIHRvIHRoaXMgW2FydGljbGVdKGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS9hLWNvbXByZWhlbnNpdmUtZ3VpZGUtdG8tdGhlLWdyYW1tYXItb2YtZ3JhcGhpY3MtZm9yLWVmZmVjdGl2ZS12aXN1YWxpemF0aW9uLW9mLW11bHRpLWRpbWVuc2lvbmFsLTFmOTJiNGVkNDE0OSk6Cgo+YSBncmFtbWFyIG9mIGdyYXBoaWNzIGlzIGEgZnJhbWV3b3JrIHdoaWNoIGZvbGxvd3MgYSBsYXllcmVkIGFwcHJvYWNoIHRvIGRlc2NyaWJlIGFuZCBjb25zdHJ1Y3QgdmlzdWFsaXphdGlvbnMgb3IgZ3JhcGhpY3MgaW4gYSBzdHJ1Y3R1cmVkIG1hbm5lci4gQSB2aXN1YWxpemF0aW9uIGludm9sdmluZyBtdWx0aS1kaW1lbnNpb25hbCBkYXRhIG9mdGVuIGhhcyBtdWx0aXBsZSBjb21wb25lbnRzIG9yIGFzcGVjdHMsIGFuZCBsZXZlcmFnaW5nIHRoaXMgbGF5ZXJlZCBncmFtbWFyIG9mIGdyYXBoaWNzIGhlbHBzIHVzIGRlc2NyaWJlIGFuZCB1bmRlcnN0YW5kIGVhY2ggY29tcG9uZW50IGludm9sdmVkIGluIHZpc3VhbGl6YXRpb27igIrigJTigIppbiB0ZXJtcyBvZiBkYXRhLCBhZXN0aGV0aWNzLCBzY2FsZSwgb2JqZWN0cyBhbmQgc28gb24uCgpZb3UgY2FuIHRoaW5rIGFib3V0IHRoZSBsYXllcmluZyBvZiBncmFwaGljcyBpbiB0aGUgc2FtZSB3YXkgYXMgY29tcG9zaXRpb24gdGhhdCB5b3UgbGVhcm50IGxhc3Qgd2Vlay4gVGhlIGlkZWEgb2YgYnVpbGRpbmcgY29tcGxleCBmdW5jdGlvbmFsaXR5IGJ5IGJyZWFraW5nIHRoaW5ncyBkb3duIGludG8gc21hbGxlciBwaWVjZXMgdXNpbmcgcGlwZXMuCgo8ZGl2IGNsYXNzPSJjZW50ZXItY29udGFpbmVyIj4KYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJhc3NldHMvaW1hZ2VzL2xheWVycyBvZiBnZ3Bsb3QucG5nIikKYGBgCjwvZGl2Pgo8c21hbGw+KipTb3VyY2UqKjogQSBDb21wcmVoZW5zaXZlIEd1aWRlIHRvIHRoZSBHcmFtbWFyIG9mIEdyYXBoaWNzIGZvciBFZmZlY3RpdmUgVmlzdWFsaXphdGlvbiBvZiBNdWx0aS1kaW1lbnNpb25hbCBEYXRhIC0gRGlwYW5qYW4gU2Fya2FyPC9zbWFsbD4KClRoZXJlIGFyZSBzZXZlbiBsYXllcnMgaW4gZ2dwbG90LiBBbGwgcGxvdHMgbmVlZCBhdGxlYXN0IHRocmVlIG91dCBvZiB0aGVzZSBzZXZlbiBsYXllcnMgKHRoZSByZXN0IGFkb3B0IGRlZmF1bHQgdmFsdWVzIGlmIG5vdCBzcGVjaWZpZWQpLiBUaGVzZSBhcmUgYXMgZm9sbG93czoKCjEuIEEgZGF0YSBsYXllcgoyLiBBbiBhZXN0aGV0aWNzIGxheWVyCjMuIEEgZ2VvbSBvciBzdGF0aXN0aWNzIGxheWVyCgpMZXRzIHN0YXJ0IHBsb3R0aW5nIQoKIyMjQmFzaWMgY2hhcnRzCkluIHRoaXMgc2VjdGlvbiB3ZSB3aWxsIGV4cGxvcmUgYSBmZXcgYmFzaWMgY2hhcnQgdHlwZXMuIExldHMgYWxzbyBvcGVuIHRoZSBbcmVmZXJlbmNlIHdlYnNpdGUgZm9yIGdncGxvdF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2luZGV4Lmh0bWwpIGluIGEgbmV3IHRhYiBzbyB0aGF0IHdlIGhhdmUgaXQgaGFuZHkuIFdlIHdpbGwgYmUgdXNpbmcgdGhlIGdhcG1pbmRlciBkYXRhc2V0IHRvIGlsbHVzdHJhdGUgZ2dwbG90IGNvbW1hbmRzLiBUaGlzIGRhdGFzZXQgaXMgYXZhaWxhYmxlIGFzIGEgcGFja2FnZSBvZiB0aGUgc2FtZSBuYW1lIFteMV0uIEdvIGFoZWFkIGFuZCBpbnN0YWxsIHRoaXMgb24geW91ciBjb21wdXRlci4gVGhlIGdhcG1pbmRlciBkYXRhc2V0IGlzIGRpc3BsYXllZCBiZWxvdy4gSXQgcHJvdmlkZXMgZGF0YSBvbiBsaWZlIGV4cGVjdGFuY3ksIHBvcHVsYXRpb24gYW5kIEdEUCBwZXIgY2FwaXRhIGZvciBlYWNoIGNvdW50cnkgZm9yIGZpdmUgeWVhciBpbnRlcnZhbHMgZnJvbSBgciBtaW4oZ2FwbWluZGVyJHllYXIpYCB0byBgciBtYXgoZ2FwbWluZGVyJHllYXIpYC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpybShsaXN0ID0gbHMoKSkKIyNsb2FkIHRoZSBwYWNrYWdlcwpsaWJyYXJ5KHRpZHl2ZXJzZSk7IGxpYnJhcnkobnljZmxpZ2h0czEzKTsgbGlicmFyeShnYXBtaW5kZXIpCmdhcG1pbmRlcgpgYGAKCiMjI1NjYXR0ZXJwbG90ClRoZSBmaXJzdCBwbG90IHdlIHdpbGwgZXhwbG9yZSBpcyB0aGUgc2NhdHRlci1wbG90LiBBIHNjYXR0ZXItcGxvdCBzaW1wbHkgcGxhY2VzIGEgcG9pbnQgb24gYSBjYXJ0ZXNpYW4gKDItRCkgY29vcmRpbmF0ZSBzeXN0ZW0gZm9yIGNvcnJlc3BvbmRpbmcgdmFsdWVzIG9mIHRoZSB4IGFuZCB5IHZhcmlhYmxlcy4gTGV0cyBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB5ZWFyIGFuZCBsaWZlIGV4cGVjdGFuY3kgdXNpbmcgYSBzY2F0dGVyLXBsb3QuIAoKR28gdGhyb3VnaCB0aGUgY29kZSBiZWxvdyB0byBpZGVudGlmeSB0aGUgZGF0YSwgYWVzdGhldGljcyBhbmQgZ2VvbSBsYXllcnMuIEFzIGNhbiBiZSBzZWVuIGJlbG93LCB0aGlzIGlzIG5vdCBhIHBhcnRpY3VsYXJseSB1c2VmdWwgY2hhcnQuIEVhY2ggeWVhciBpbiB0aGUgZGF0YSBoYXMgc2V2ZXJhbCBvYnNlcnZhdGlvbnMgZm9yIGxpZmUgZXhwZWN0YW5jeSAob25lIGZvciBlYWNoIGNvdW50cnkpLCB0aGlzIHJlc3VsdHMgaW4gYSBjaGFydCBmb3Igd2hpY2ggaXQgaXMgZGlmZmljdWx0IHRvIHBlcmNlaXZlIGEgY2xlYXIgdHJlbmQuIEluIHRoZSBuZXh0IHN0ZXAgd2Ugd2lsbCBzZWUgaG93IHRvIGltcHJvdmUgdGhpcyBjaGFydC4KCmBgYHtyfQojI3NwZWNpZnkgdGhlIGRhdGEgYW5kIGFlc3RoZXRpY3MgbGF5ZXIKZ2dwbG90KGRhdGEgPSBnYXBtaW5kZXIsIGFlcyh4ID0geWVhciwgeSA9IGxpZmVFeHApKSArCiAgICAjI3NwZWNpZnlpbmcgdGhlIGdlb20gdHlwZQogICAgZ2VvbV9wb2ludCgpICsKICAgICMjYWRkaW5nIHggYW5kIHkgYXhpcyB0aXRsZXMKICAgIGxhYnMoeCA9ICJZZWFyIiwgeSA9ICJMaWZlIEV4cGVjdGFuY3kiKQpgYGAKCuKaoSoqKk5pbmphIFRhc2tzKioq4pqhCgoxLiBVc2UgYSBzY2F0dGVyLXBsb3QgdG8gZXhwbG9yZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gR0RQIHBlciBjYXBpdGEgYW5kIGxpZmUgZXhwZWN0YW5jeQoyLiBCb251cyBxdWVzdGlvbjogQ2FuIHlvdSBtYXAgdGhlIHBvcHVsYXRpb24gdG8gYSBwYXJ0aWN1bGFyIGFlc3RoZXRpYyBzbyB0aGF0IGl0IGNhbiBiZSBkaXNwbGF5ZWQgb24gdGhlIGNoYXJ0IHRvbz8KCvCfj4YqKipTb2x1dGlvbioqKvCfj4YKCiMjIyMxLiBHRFAgcGVyIGNhcGl0YSB2cyBsaWZlIGV4cGVjdGFuY3kKVGhlIGdhcG1pbmRlciBkYXRhIGNvbnNpc3RzIG9mIG9ic2VydmF0aW9ucyBmb3IgZWFjaCBjb3VudHJ5IGZvciBlYWNoIHllYXIuIFdlIGFyZSBpbnRlcmVzdGVkIGluIGNhcHR1cmluZyB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gR0RQIHBlciBjYXBpdGEgYW5kIHRoZSBsaWZlIGV4cGVjdGFuY3kuIEhvd2V2ZXIsIHNpbmNlIHRoZSBkYXRhIGlzIGEgdGltZSBzZXJpZXMsIHdlIG5lZWQgdG8gYXZlcmFnZSB0aGUgR0RQIHBlciBjYXBpdGEgYW5kIGxpZmUgZXhwZWN0YW5jeSBmb3IgZWFjaCBjb3VudHJ5IGZvciB0aGUgZW50aXJlIHRpbWUgc2VyaWVzIGJlZm9yZSBwbG90dGluZyB0byBzbW9vdGggb3V0IHRpbWUgdHJlbmRzIGFuZCBhdm9pZCBvdmVyLXBsb3R0aW5nIFteMl0uIFRoaXMgd291bGQgYWxsb3cgdXMgdG8gb2JzZXJ2ZSB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHR3byB2YXJpYWJsZXMgbW9yZSBjbGVhcmx5IHdpdGhvdXQgYW55IGNvbmZ1c2luZyB0aW1lIHRyZW5kcyBbXjNdLgoKYGBge3J9CmdhcG1pbmRlciAlPiUgCiAgICAjI2ZpbmQgdGhlIG1lYW4gZ2RwIHBlciBjYXBpdGEgYW5kIGxpZmUgZXhwZWN0YW5jeSBmb3IgZWFjaCBjb3VudHJ5CiAgICBncm91cF9ieShjb3VudHJ5KSAlPiUgCiAgICBzdW1tYXJpc2UobWVhbkdEUHBlckNhcCA9IG1lYW4oZ2RwUGVyY2FwLCBuYS5ybSA9IFQpLCAKICAgICAgICAgICAgICBtZWFuTGlmZUV4cCA9IG1lYW4obGlmZUV4cCwgbmEucm0gPSBUKSkgJT4lIAogICAgIyNzcGVjaWZ5IHRoZSBkYXRhIGFuZCBhZXN0aGV0aWNzIGxheWVyCiAgICBnZ3Bsb3QoLiwgYWVzKHggPSBtZWFuR0RQcGVyQ2FwLCB5ID0gbWVhbkxpZmVFeHApKSArIAogICAgIyNzcGVjaWZ5IHRoZSBnZW9tCiAgICBnZW9tX3BvaW50KGNvbG91ciA9ICJibHVlIiwgc2l6ZSA9IDIuNSwgYWxwaGEgPSAwLjgpICsKICAgICMjc3BlY2lmeSB0aXRsZXMKICAgIGxhYnMoeCA9ICJNZWFuIEdEUCBwZXIgY2FwaXRhIiwgCiAgICAgICAgIHkgPSAiTWVhbiBMaWZlIEV4cGVjdGFuY3kiLCAKICAgICAgICAgdGl0bGUgPSAiTGlmZSBFeHBlY3RhbmN5IGluY3JlYXNlcyB3aXRoIEdEUCBwZXIgY2FwaXRhIikKYGBgCgpUaGUgc2NhdHRlci1wbG90IHNob3dzIG9uZSBzaW5nbGUgcG9pbnQgZm9yIGVhY2ggY291bnRyeSBpbiB0aGUgZGF0YXNldCByZXByZXNlbnRpbmcgdGhlIG1lYW4gbGlmZSBleHBlY3RhbmN5IGFuZCBHRFAgcGVyIGNhcGl0YS4gTWFrZSBzdXJlIHRvIHJlYWQgdGhlIGNvZGUgdXNlZCB0byBnZW5lcmF0ZSB0aGUgY2hhcnQgY2FyZWZ1bGx5IHNvIHRoYXQgeW91IHVuZGVyc3RhbmQgaXQgZnVsbHkuIFdoeSBhcmUgdGhlIGNvbG91ciBhbmQgc2l6ZSBwYXJhbXRlcnMgaW4gdGhlIGBnZW9tX3BvaW50KClgIG5vdCBpbnNpZGUgYW4gYGFlcygpYD8KCiMjIyMzLiBNYXAgYW4gYWRkaXRpb25hbCBhZXN0aGV0aWMgdG8gdGhlIHBsb3QKCkxldHMgbWFwIHRoZSBzaXplIG9mIGVhY2ggcG9pbnQgdG8gdGhlIGBtZWFuUG9wYCB2YXJpYWJsZS4gSSBhbHNvIGludHJvZHVjZSBhbiBhZGRpdGlvbmFsIGFlc3RoZXRpYyBtYXBwaW5nIG9mIGNvbG91ciB0byB0aGUgY29udGluZW50IHZhcmlhYmxlLiBJbiBhZGRpdGlvbiwgc2luY2UgdGhlcmUgYXJlIGxhcmdlIHZhbHVlcyBpbiB0aGUgR0RQIHBlciBjYXBpdGEsIHdlIGNhbiBwbG90IHRoZSBsb2cgb2YgdGhlIG1lYW4gR0RQIHBlciBjYXBpdGEgdG8gc21vb3RoIG91dCB0aGUgbGFyZ2UgbnVtYmVycyAoPHNwYW4gY2xhc3M9ImhpZ2hsaWdodCI+W3dhdGNoXSgjbG9ncyk8L3NwYW4+IHRoZSB2aWRlbyBiZWxvdyBpZiB5b3UgZG9uJ3QgZnVsbHkgdW5kZXJzdGFuZCBsb2dhcml0aG1pYyBzY2FsZXMpLiAKCldlIGNhbiBkbyB0aGlzIGluIHR3byB3YXlzLCBmaXJzdCB3ZSBjb3VsZCBtdXRhdGUgYG1lYW5HRFBwZXJDYXBgIGJ5IHRha2luZyBpdHMgbG9nIGFuZCBwbG90dGluZyB0aGF0ICh0cnkgdGhpcyB5b3Vyc2VsZilbXjRdLCBhbmQgdGhlIHNlY29uZCBvcHRpb24gd291bGQgYmUgdG8gdXNlIGdncGxvdHMgaW5idWlsdCBgc2NhbGVfeF9sb2cxMGAgY29tbWFuZCB0byB0cmFuc2Zvcm0gdGhlIHBsb3R0aW5nIHNjYWxlIGluc3RlYWQgb2YgdGhlIHZhcmlhYmxlLiBXaGlsZSB0aGUgY2hhcnRzIHdvdWxkIGxhcmdlbHkgbG9vayB0aGUgc2FtZSwgdGhlIHNlY29uZCBvcHRpb24gcHJlc2VydmVzIHRoZSBhY3R1YWwgdmFyaWFibGUgYW5kIHBsb3RzIGl0IG9uIHRoZSBuZXcgc2NhbGUuIFBsb3QgdGhlIGZpcnN0IG9wdGlvbiBvbiB5b3VyIG93biB0byBzZWUgaWYgeW91IGNhbiBzcG90IHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIHBsb3QgYmVsb3cgYW5kIHRoYXQgb25lLgoKVGhlIGFwcGxpY2F0aW9uIG9mIHRoZSBsb2cgc2NhbGUgaGFzIHRoZSBlZmZlY3Qgb2YgbWFraW5nIGl0IGFwcGVhciBhcyBpZiB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gR0RQIHBlciBjYXBpdGEgYW5kIGxpZmUgZXhwZWN0YW5jeSBpcyBsaW5lYXIuIDxzcGFuIGNsYXNzPSJoaWdobGlnaHQiPlRoaXMgaXMgbm90IHRydWU8L3NwYW4+LCBzaW5jZSBhcyB3ZSBvYnNlcnZlZCBpbiB0aGUgcHJldmlvdXMgY2hhcnQgdGhlIHRydWUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlc2UgdHdvIHZhcmlhYmxlcyBpcyBwYXJhYm9saWMuIEFzIEdEUCBpbmNyZWFzZXMsIHRoZSBjb3JyZXNwb25kaW5nIGluY3JlYXNlIGluIGxpZmUgZXhwZWN0YW5jeSBnZXRzIGxvd2VyIG92ZXIgdGltZSAocHJvYmFibHkgYmVjYXVzZSB3ZSBjYW4ndCBleHRlbmQgaHVtYW4gbGlmZXNwYW5zIGJleW9uZCBhIGNlcnRhaW4gbGltaXQsIG5vIG1hdHRlciBob3cgaGFyZCB3ZSB0cnkpLiBUaGlzIGlzIG9uZSByZWFzb24sIHdoeSB3ZSBzaG91bGQgYmUgZXh0cmVtZWx5IGNhcmVmdWwgd2l0aCBzY2FsZSB0cmFuc2Zvcm1hdGlvbnMuIFdlIHNob3VsZCBvbmx5IHVzZSB0aGVtIGlmIHdlIGhhdmUgYSBjbGVhciByZWFzb24gZm9yIHdoeSB0aGV5IGFyZSBuZWNlc3NhcnkuIEFuZCB3aGVuIHdlIGRvIGNob29zZSB0byB1c2UgdGhlbSB3ZSBzaG91bGQgaGF2ZSBhIGNsZWFyIHNlbnNlIGZvciB3aGF0IHRoZXkgYXJlIGRvaW5nIHRvIHRoZSBpbnRlcnByZXRhdGlvbiBvZiB0aGUgY2hhcnQuCmBgYHtyfQpnYXBtaW5kZXIgJT4lIAogICAgIyNDYWxjdWxhdGUgY291bnRyeSBsZXZlbCBpbmZvcm1hdGlvbgogICAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIAogICAgc3VtbWFyaXNlKG1lYW5HRFBwZXJDYXAgPSBtZWFuKGdkcFBlcmNhcCwgbmEucm0gPSBUKSwgCiAgICAgICAgICAgICAgbWVhbkxpZmVFeHAgPSBtZWFuKGxpZmVFeHAsIG5hLnJtID0gVCksIAogICAgICAgICAgICAgIG1lYW5Qb3AgPSBtZWFuKHBvcCwgbmEucm0gPSBUKSwKICAgICAgICAgICAgICBjb250aW5lbnQgPSB1bmlxdWUoY29udGluZW50KSkgJT4lIAogICAgIyNzcGVjaWZ5IGRhdGEgYW5kIGFlc3RoZXRpY3MKICAgIGdncGxvdCguLCBhZXMoeCA9IG1lYW5HRFBwZXJDYXAsIAogICAgICAgICAgICAgICAgICB5ID0gbWVhbkxpZmVFeHAsIAogICAgICAgICAgICAgICAgICBzaXplID0gbWVhblBvcCwgCiAgICAgICAgICAgICAgICAgIGNvbG91ciA9IGNvbnRpbmVudCkpICsgCiAgICAjI3NwZWNpZnkgdGhlIGdlb20KICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICAgICMjYXBwbHkgc2NhbGUgdHJhbnNmb3JtYXRpb24KICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICAjI3NwZWNpZnkgdGhlIHRpdGxlcwogICAgbGFicyh4ID0gIk1lYW4gR0RQIHBlciBjYXBpdGEiLCAKICAgICAgICAgeSA9ICJNZWFuIExpZmUgRXhwZWN0YW5jeSIsIAogICAgICAgICB0aXRsZSA9ICJMaWZlIEV4cGVjdGFuY3kgaW5jcmVhc2VzIGV4dGggR0RQIHBlciBjYXBpdGEiLCAKICAgICAgICAgc2l6ZSA9ICJNZWFuIHBvcHVsYXRpb24iLAogICAgICAgICBjb2xvdXIgPSAiQ29udGluZW50IikgKwogICAgIyNhZGp1c3QgdGhlIHBvc2l0aW9uIGFuZCBsYXlvdXQgb2YgdGhlIGxlZ2VuZHMKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLAogICAgICAgICAgbGVnZW5kLmJveCA9ICJ2ZXJ0aWNhbCIsCiAgICAgICAgICBsZWdlbmQuc3BhY2luZy55ID0gdW5pdCgtOCwgInB0IiksCiAgICAgICAgICApCmBgYAoKPGRpdiBjbGFzcz0iY2VudGVyLWNvbnRhaW5lciI+CjxhIG5hbWU9ImxvZ3MiPjxpZnJhbWUgd2lkdGg9IjcyMCIgaGVpZ2h0PSI0MDUiIHNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvc0JoRWk0TDkxU2ciIGZyYW1lYm9yZGVyPSIwIiBhbGxvdz0iYXV0b3BsYXk7IGVuY3J5cHRlZC1tZWRpYSIgYWxsb3dmdWxsc2NyZWVuPjwvaWZyYW1lPjwvYT4KPC9kaXY+CgojIyMgTGluZSBjaGFydApOb3cgbGV0cyB1c2UgZHBseXIgdG8gY2FsY3VsYXRlIHRoZSBhdmVyYWdlIGdsb2JhbCBsaWZlIGV4cGVjdGFuY3kgYnkgeWVhciBhbmQgcGxvdCB0aGF0IHVzaW5nIGEgbGluZS4gQXMgY2FuIGJlIHNlZW4gYmVsb3csIHRoZSBhdmVyYWdlIGdsb2JhbCBsaWZlIGV4cGVjdGFuY3kgaGFzIGJlZW4gaW5jcmVhc2luZyBvdmVyIHRoZSB5ZWFycy4KCmBgYHtyfQpnYXBtaW5kZXIgJT4lIAogICAgIyNncm91cCBieSB5ZWFyIHRvIGNhbGN1bGF0ZSB0aGUgeWVhcmx5IGF2ZXJhZ2UgbGlmZSBleHBlY3RhbmN5CiAgICBncm91cF9ieSh5ZWFyKSAlPiUgCiAgICBzdW1tYXJpc2UobWVhbkxpZmVFeHAgPSBtZWFuKGxpZmVFeHAsIG5hLnJtID0gVCkpICU+JSAKICAgICMjZHJhdyBhIGxpbmUgbWFya2luZyB0aGUgdHJlbmQgb2YgbGlmZSBleHBlY3RhbmN5IG92ZXIgeWVhcnMKICAgIGdncGxvdCguLCBhZXMoeCA9IHllYXIsIHkgPSBtZWFuTGlmZUV4cCkpICsKICAgIGdlb21fbGluZSgpICsKICAgIGxhYnModGl0bGUgPSAiQXZlcmFnZSBsaWZlIGV4cGVjdGFuY3kgaGFzIGJlZW4gaW5jcmVhc2luZyIsIHggPSAiWWVhcnMiLCB5ID0gIk1lYW4gTGlmZSBFeHBlY3RhbmN5IikKYGBgCgoKTncgbGV0cyBnbyBhIGJpdCBmdXJ0aGVyIGFuZCBleHBsb3JlIHRoZSB0cmVuZCBmb3IgbGlmZSBleHBlY3RhbmN5IGZvciBkaWZmZXJlbnQgY291bnRyaWVzLiBObwpgYGB7cn0KZ2dwbG90KGRhdGEgPSBnYXBtaW5kZXIsIG1hcHBpbmcgPSBhZXMoeCA9IHllYXIsIHkgPSBsaWZlRXhwKSkgKwogICAgIyNjb2xvdXIgYW5kIGdyb3VwIGFyZSBtYXBwZWQgdG8gY29udGluZW50IGFuZCBjb3VudHJ5IGFuZCBhZXN0aGV0aWMgaXMgc2V0IHRvIDAuNQogICAgZ2VvbV9saW5lKG1hcHBpbmcgPSBhZXMoY29sb3VyID0gY29udGluZW50LCBncm91cCA9IGNvdW50cnkpLCBhbHBoYSA9IDAuNSkgKwogICAgbGFicyh0aXRsZSA9ICJDb3VudHJpZXMgaW4gQWZyaWNhIGhhdmUgbG93ZXIgbGlmZSBleHBlY3RhbmN5IiwgeCA9ICJZZWFyIiwgeSA9ICJMaWZlIEV4cGVjdGFuY3kiKSArCiAgICB0aGVtZSgKICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAidG9wIgogICAgICAgIAogICAgKQpgYGAKCkluIHRoZSBjaGFydCBhYm92ZSB0aGVyZSBhcmUgYSBmZXcgY291bnRyaWVzIHdlcmUgdGhlcmUgYXJlIHN1ZGRlbiBkcm9wcyBpbiBsaWZlIGV4cGVjdGFuY3kuIFRoZXNlIGFyZSBiZWNhdXNlIG9mIGdlbm9jaWRlcyB0aGF0IG9jY3VycmVkIGluIFJ3YW5kYSwgQ2FtYm9kaWEgYW5kIENoaW5hLiBUaGUgY2hhcnQgYmVsb3cgdXNlZCBjb2xvciBhbmQgYWxwaGEgbWFwcGluZyB0byBoaWdobGlnaHQgdGhlc2UgdmFsdWVzLiBOb3RpY2UgaG93LCBJIHVzZSBhIGFscGhhTWFwcGluZyBhbmQgY29sb3JNYXBwaW5nIHZhcmlhYmxlcyB0byBmaXggdGhlIGFscGhhIGFuZCBjb2xvdXIgYWVzdGhldGljcyBpbiB0aGUgY2hhcnQuW142XSAKCgpgYGB7cn0KIyNyZXBlYXQgdGhlIHNhbWUgYnV0IHdpdGggb25lIGxpbmUgZm9yIGVhY2ggY29udGluZW50L2NvdW50cnksIChndWVzcyB3aHkgdGhlcmUgaXMgYSBzdWRkZW4gZHJvcCBmb3IgYSBmZXcgY291bnRyaWVzKT8KYWxwaGFNYXBwaW5nIDwtIGlmX2Vsc2UoZ2FwbWluZGVyJGNvdW50cnkgJWluJSBjKCJSd2FuZGEiLCAiQ2FtYm9kaWEiLCAiQ2hpbmEiKSwgMC44LCAwLjEpCmNvbG9yTWFwcGluZyA8LSBpZl9lbHNlKGdhcG1pbmRlciRjb3VudHJ5ICVpbiUgYygiUndhbmRhIiwgIkNhbWJvZGlhIiwgIkNoaW5hIiksICJkYXJrcmVkIiwgImJsYWNrIikKCmdncGxvdChkYXRhID0gZ2FwbWluZGVyLCBtYXBwaW5nID0gYWVzKHggPSB5ZWFyLCB5ID0gbGlmZUV4cCwgZ3JvdXAgPSBjb3VudHJ5KSkgKwogICAgZ2VvbV9saW5lKGFscGhhID0gYWxwaGFNYXBwaW5nLCBjb2xvdXIgPSBjb2xvck1hcHBpbmcpICsKICAgIGxhYnModGl0bGUgPSAiVGhlIGltcGFjdCBvZiBnZW5vY2lkZXMgb24gbGlmZSBleHBlY3RhbmN5IiwgeCA9ICJZZWFyIiwgeSA9ICJMaWZlIEV4cGVjdGFuY3kiKQpgYGAKCgrimqEqKipOaW5qYSBUYXNrcyoqKuKaoQoKMS4gRHJhdyBhIGxpbmUgY2hhcnQgc2hvd2luZyB0aGUgdHJlbmQgZm9yIGF2ZXJhZ2UgR0RQIHBlciBjYXBpdGEgdGhyb3VnaCB0aW1lIGZvciBlYWNoIGNvbnRpbmVudAoK8J+PhioqKlNvbHV0aW9uKioq8J+PhgpUaGUgY2hhcnQgYmVsb3cgc2hvd3MgdGhhdCBwb29yZXIgY291bnRyaWVzIGluIHRoZSBBZnJpY2EgYW5kIEFzaWEgYXJlIHN0aWxsIGxhZ2dpbmcgYmVoaW5kIHRob3NlIGluIHRoZSBXZXN0LiBBY2NvcmRpbmcgdG8gW1dpa2lwZWRpYV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvQ29udmVyZ2VuY2VfKGVjb25vbWljcykpIGNvbnZlcmdlbmNlIGNhbiBiZSBkZXNjcmliZWQgYXMgZm9sbG93czoKCj5UaGUgaWRlYSBvZiBjb252ZXJnZW5jZSBpbiBlY29ub21pY3MgKGFsc28gc29tZXRpbWVzIGtub3duIGFzIHRoZSBjYXRjaC11cCBlZmZlY3QpIGlzIHRoZSBoeXBvdGhlc2lzIHRoYXQgcG9vcmVyIGVjb25vbWllcycgcGVyIGNhcGl0YSBpbmNvbWVzIHdpbGwgdGVuZCB0byBncm93IGF0IGZhc3RlciByYXRlcyB0aGFuIHJpY2hlciBlY29ub21pZXMuIEFzIGEgcmVzdWx0LCBhbGwgZWNvbm9taWVzIHNob3VsZCBldmVudHVhbGx5IGNvbnZlcmdlIGluIHRlcm1zIG9mIHBlciBjYXBpdGEgaW5jb21lLgoKYGBge3J9CmdhcG1pbmRlciAlPiUgCiAgICBncm91cF9ieShjb250aW5lbnQsIHllYXIpICU+JSAKICAgIHN1bW1hcmlzZShtZWFuR0RQID0gbWVhbihnZHBQZXJjYXAsIG5hLnJtID0gVCkpICU+JSAKICAgIGdncGxvdCguLCBhZXMoeCA9IHllYXIsIHkgPSBtZWFuR0RQKSkgKwogICAgZ2VvbV9saW5lKGFlcyhjb2xvdXIgPSBjb250aW5lbnQpKSArCiAgICBsYWJzKHggPSAiWWVhciIsIHkgPSAiTWVhbiBHRFAgcGVyIENhcGl0YSIsIHRpdGxlID0gIkNvbnZlcmdlbmNlIHdoZXJlIGFydCB0aG91PyIsIGNvbG91ciA9ICJDb250aW5lbnQiKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgoKCjMuIEhpc3RvZ3JhbQpXYXRjaCB0aGUgdmlkZW8gW2JlbG93XSgjaGlzdCkgdG8gcmVmcmVzaCB5b3VyIHVuZGVyc3RhbmRpbmcgb2YgaGlzdG9ncmFtcy4gVGhlIHBsb3QgYmVsb3cgc2hvd3MgdGhlIGhpc3RvZ3JhbSBvZiBgZ2RwUGVyQ2FwYCBpbiB0aGUgZ2FwbWluZGVyIGRhdGFzZXQuIFRoZXJlIGlzIGhvd2V2ZXIgYSBwcm9ibGVtIHdpdGggdGhpcyBjaGFydC4gSXQgc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiBHRFAgcGVyIGNhcGl0YSBvdmVyIHRpbWUuIFdlIGFyZSBob3dldmVyIG5vdCBpbnRlcmVzdGVkIGluIHRoZSBzcHJlYWQgb2YgR0RQIHBlciBjYXBpdGEgYWNyb3NzIHRpbWUsIHJhdGhlciBvdXIgaW50ZXJlc3QgaXMgaW4gc2VlaW5nIHRoZSBzcHJlYWQgYWNyb3NzIGNvdW50cmllcy4gSW4gdGhpcyBjYXNlLCB3ZSBtaWdodCBiZSBiZXR0ZXIgb2ZmIGJ5IGZpbHRlcmluZyBkb3duIHRvIGEgc2luZ2xlIHllYXIgYW5kIG9ic2VydmluZyB0aGUgZGlzdHJpYnV0aW9uIG9mIEdEUCBwZXIgY2FwaXRhIGFjcm9zcyBjb3VudHJpZXMuIExldHMgdHJ5IHRoaXMgb3V0IHdpdGggdGhlIHRyYWluaW5nIGV4ZXJjaXNlLgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBnYXBtaW5kZXIsIGFlcyhnZHBQZXJjYXApKSArCiAgICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMzApCmBgYAo8ZGl2IGNsYXNzPSJjZW50ZXItY29udGFpbmVyIj4KPGEgaWQ9Imhpc3QiPjxpZnJhbWUgd2lkdGg9IjU2MCIgaGVpZ2h0PSIzMTUiIHNyYz0iaHR0cHM6Ly93d3cueW91dHViZS5jb20vZW1iZWQvZ1NFWXRBanVaLVkiIGZyYW1lYm9yZGVyPSIwIiBhbGxvdz0iYXV0b3BsYXk7IGVuY3J5cHRlZC1tZWRpYSIgYWxsb3dmdWxsc2NyZWVuPjwvaWZyYW1lPjwvYT4KPC9kaXY+CgrimqEqKipOaW5qYSBUYXNrcyoqKuKaoQoKMS4gRHJhdyBhIGhpc3RvZ3JhbSB0byBzaG93IHRoZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgcG9wdWxhdGlvbgoK8J+PhioqKlNvbHV0aW9uKioq8J+PhgoKRm9yIHRoaXMgY2hhcnQgd2Ugc2VsZWN0IHRoZSBsYXRlc3QgeWVhciBpbiB0aGUgZGF0YXNldCBhbmQgcGxvdCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBwb3B1bGF0aW9uLiBDb3VsZCB5b3UgdGhpbmsgb2YgdGhlIHByb3MgYW5kIGNvbnMgZm9yIHVzaW5nIHRoaXMgbWV0aG9kIHZlcnN1cyB0aGUgb25lIGluIHdoaWNoIHdlIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBwb3B1bGF0aW9uIGZvciBlYWNoIGNvdW50cnkgb3ZlciB0aGUgZW50aXJlIGRhdGFzZXQgYW5kIHBsb3R0aW5nIHRoYXQ/CgpgYGB7cn0KZ2FwbWluZGVyICU+JSAKICAgIGZpbHRlcih5ZWFyID09IG1heCh5ZWFyLCBuYS5ybSA9IFQpKSAlPiUgCiAgICBnZ3Bsb3QoLiwgYWVzKHBvcCkpICsKICAgIGdlb21faGlzdG9ncmFtKGJpbnMgPSA2MCkgKwogICAgbGFicyh4ID0gIlBvcHVsYXRpb24iLCB5ID0gIkNvdW50IikKYGBgCgojIyNCYXIgY2hhcnQKVGhlIGNoYXJ0IGJlbG93IHNob3dzIHRoZSBhdmVyYWdlIGxpZmUgZXhwZWN0YW5jeSBmb3IgZGlmZmVyZW50IGNvbnRpbmVudHMgZm9yIHRoZSBtb3N0IHJlY2VudCB5ZWFyIGluIHRoZSBkYXRhLiBDYW4geW91IHRoaW5rIG9mIHRoZSByZWFzb24gd2h5IGl0IG1pZ2h0IGJlIHNsaWdodGx5IGJldHRlciB0byBoYXZlIG9ubHkgY29uc2lkZXJlZCB0aGUgbW9zdCByZWNlbnQgeWVhciB3aGVuIGNhbGN1bGF0aW5nIHRoZSBhdmVyYWdlIGxpZmUgZXhwZWN0YW5jeSBmb3IgYSBjb250aW5lbnQ/IEFsc28gbm90aWNlLCBob3cgdGhlIGJhcnMgYXJlIGFsaWduZWQgZnJvbSB0aGUgc21hbGxlc3QgdG8gdGhlIHRhbGxlc3QuIENhbiB5b3UgZmluZCBvdXQgaG93IEkgbWlnaHQgaGF2ZSBhY2hpZXZlZCB0aGlzPwpgYGB7cn0KIyNkcmF3IGEgYmFyIGNoYXJ0IHdpdGggYXZlcmFnZSBsaWZlIGV4cGVjdGFuY3kgaW4gZGlmZmVyZW50IGNvbnRpbmVudHMKZ2FwbWluZGVyICU+JSAKICAgIGZpbHRlcih5ZWFyID09IG1heCh5ZWFyLCBuYS5ybSA9IFQpKSAlPiUgCiAgICBncm91cF9ieShjb250aW5lbnQpICU+JSAKICAgIHN1bW1hcmlzZShtZWFuTGlmZUV4cCA9IG1lYW4obGlmZUV4cCwgbmEucm0gPSBUKSkgJT4lIAogICAgZ2dwbG90KC4sIGFlcyh4ID0gcmVvcmRlcihjb250aW5lbnQsIG1lYW5MaWZlRXhwKSwgeSA9IG1lYW5MaWZlRXhwKSkgKwogICAgZ2VvbV9jb2woKSArCiAgICBsYWJzKHkgPSAiTWVhbiBsaWZlIGV4cGVjdGFuY3kiKSArCiAgICB0aGVtZSgKICAgICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCkKICAgICkKICAgIApgYGAKCuKaoSoqKk5pbmphIFRhc2tzKioq4pqhCgoxLiBEcmF3IGEgYmFyIGNoYXJ0IHNob3dpbmcgdGhlIGF2ZXJhZ2UgbGlmZUV4cCBhY3Jvc3MgeWVhcnMKCvCfj4YqKipTb2x1dGlvbioqKvCfj4YKYGBge3J9CmdhcG1pbmRlciAlPiUgCiAgICBncm91cF9ieSh5ZWFyKSAlPiUgCiAgICBzdW1tYXJpc2UobWVhbkxpZmVFeHAgPSBtZWFuKGxpZmVFeHAsIG5hLnJtID0gVCkpICU+JSAKICAgIGdncGxvdChkYXRhID0gLiwgYWVzKHggPSB5ZWFyLCB5ID0gbWVhbkxpZmVFeHApKSArCiAgICBnZW9tX2NvbCgpCiAgICAKYGBgCgpgYGB7cn0KZ2FwbWluZGVyICU+JSAKICAgIGdyb3VwX2J5KHllYXIpICU+JSAKICAgIHN1bW1hcmlzZShtZWFuR0RQID0gbWVhbihnZHBQZXJjYXAsIG5hLnJtID0gVCkpICU+JSAKICAgIGdncGxvdCguLCBhZXMoeCA9IHllYXIsIHkgPSBtZWFuR0RQKSkgKwogICAgZ2VvbV9jb2woKQpgYGAKCiMjI0ZhY2V0dGluZwoKYGBge3J9CiMjcmV1c2Ugc29tZSBvZiB0aGUgcGxvdHMgYWJvdmUgYXMgYSBmYWNldHRlZCBwbG90CmdncGxvdChnYXBtaW5kZXIsIGFlcyh4ID0geWVhciwgeSA9IGxpZmVFeHApKSArCiAgICBnZW9tX2ppdHRlcih3aWR0aCA9IDEuNSwgYWxwaGEgPSAwLjYpICsKICAgIGZhY2V0X3dyYXAofmNvbnRpbmVudCkKYGBgCgoK4pqhKioqTmluamEgVGFza3MqKirimqEKCjEuIFNob3cgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIEdEUCBwZXIgY2FwaXRhIGFuZCBMaWZlIEV4cGVjdGFuY3kgdXNpbmcgYSBmYWNldGVkIGNoYXJ0IG9mIHlvdXIgY2hvaWNlCgoKCgojIyNMYXllcmluZyBtdWx0aXBsZSBnZW9tcwoKCmBgYHtyfQojI2RyYXcgYSBsaW5lIG9mIGZpdCBpbiBhIGNoYXJ0IG9mIHBvcHVsYXRpb24gYW5kIGxpZmUgZXhwZWN0YW5jeQpnYXBtaW5kZXIgJT4lIAogICAgZ3JvdXBfYnkoY291bnRyeSkgJT4lIAogICAgc3VtbWFyaXNlKGNvbnRpbmVudCA9IHVuaXF1ZShjb250aW5lbnQpLCBtZWFuUG9wID0gbWVhbihwb3AsIG5hLnJtID0gVCksIG1lYW5MaWZlRXhwID0gbWVhbihsaWZlRXhwLCBuYS5ybSA9IFQpKSAlPiUgCiAgICBmaWx0ZXIobWVhblBvcCA8IDVlNykgJT4lIApnZ3Bsb3QoLiwgYWVzKHggPSBtZWFuUG9wLCB5ID0gbWVhbkxpZmVFeHApKSArCiAgICBnZW9tX2ppdHRlcigpICsgCiAgICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iLCBzZSA9IEYpCmBgYAoKCuKaoSoqKk5pbmphIFRhc2tzKioq4pqhCgoxLiBBZGQgYSBsaW5lIG9mIGJlc3QgZml0IHRvIGEgc2NhdHRlciBwbG90IHNob3dpbmcgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIEdEUCBwZXIgY2FwaXRhIGFuZCBsaWZlIGV4cGVjdGFuY3kKCiMjI0FkZGluZyBzdW1tYXJ5IHN0YXRzIHRvIHBsb3RzCgoKYGBge3J9CiMjZXhwbG9yZSBnZHAgcGVyIGNhcGl0YSBvdmVyIHRoZSB5ZWFycyB1c2luZyBhIGppdHRlciBwbG90IGFuZCBzdGF0X3N1bW1hcnkgKHJ1bGUgb2YgdGh1bWIsIGFsd2F5cyBzdGF5IGFzIGNsb3NlIHRvIHRoZSBkYXRhIGFzIHBvc3NpYmxlKQoKICAgIApgYGAKCgoKCmBgYHtyfQpnZ3Bsb3QoZ2FwbWluZGVyLCBhZXMoeCA9IHllYXIsIHkgPSBnZHBQZXJjYXApKSArCiAgICBnZW9tX2ppdHRlcigpCmBgYAoKYGBge3J9CiMjVXNpbmcgdmxpbmUgYW5kIGhsaW5lCgpgYGAKCgoK4pqhKioqTmluamEgVGFza3MqKirimqEKCjEuIEFkZCBhIHZlcnRpY2FsIGxpbmUgc2hvd2luZyB0aGUgbWVhbiBvZiBwb3B1bGF0aW9uIHRvIHRoZSBoaXN0b2dyYW0gc2hvd2luZyBpdHMgZGlzdHJpYnV0aW9uCgoKIyMjQWRkIHNvbWUgc3R5bGUKCgpgYGB7cn0KIyNVc2UgdGhlIHBsb3QgZnJvbSB0aGUgcHJldmlvdXMgc2VjdGlvbiBhbmQgYWRkIHNvbWUgcGl6YXp6IChleHBsb3JlIGdndGhlbWVzLCBsZWdlbmQgcG9zaXRpb24gZXRjKQpgYGAKCgoKCiMjI0FkZCBzb21lIG1vcmUgY29tcGxleGl0eSAoYmFzZWQgb24gdGltZSkKCiMjIyNDb29yZHMKCiMjIyNTY2FsZXMKCgojI0RhdGEgSm9pbnMKClRoZXJlIGFyZSA2IGRpZmZlcmVudCB0eXBlcyBvZiBqb2lucy4gVGhlc2UgYXJlIGFzIGZvbGxvd3M6CgoxLiBgaW5uZXJfam9pbigpYDogcmV0dXJuIDxzcGFuIGNsYXNzPSJoaWdobGlnaHQiPmFsbCByb3dzIGZyb20geCB3aGVyZSB0aGVyZSBhcmUgbWF0Y2hpbmcgdmFsdWVzIGluIHk8L3NwYW4+LCBhbmQgYWxsIGNvbHVtbnMgZnJvbSB4IGFuZCB5LiBJZiB0aGVyZSBhcmUgbXVsdGlwbGUgbWF0Y2hlcyBiZXR3ZWVuIHggYW5kIHksIGFsbCBjb21iaW5hdGlvbiBvZiB0aGUgbWF0Y2hlcyBhcmUgcmV0dXJuZWQKMi4gYGxlZnRfam9pbigpYDogcmV0dXJuIDxzcGFuIGNsYXNzPSJoaWdobGlnaHQiPmFsbCByb3dzIGZyb20geDwvc3Bhbj4sIGFuZCBhbGwgY29sdW1ucyBmcm9tIHggYW5kIHkuIFJvd3MgaW4geCB3aXRoIG5vIG1hdGNoIGluIHkgd2lsbCBoYXZlIE5BIHZhbHVlcyBpbiB0aGUgbmV3IGNvbHVtbnMuIElmIHRoZXJlIGFyZSBtdWx0aXBsZSBtYXRjaGVzIGJldHdlZW4geCBhbmQgeSwgYWxsIGNvbWJpbmF0aW9ucyBvZiB0aGUgbWF0Y2hlcyBhcmUgcmV0dXJuZWQuCjMuIGByaWdodF9qb2luKClgOiByZXR1cm4gPHNwYW4gY2xhc3M9ImhpZ2hsaWdodCI+YWxsIHJvd3MgZnJvbSB5PC9zcGFuPiwgYW5kIGFsbCBjb2x1bW5zIGZyb20geCBhbmQgeS4gUm93cyBpbiB5IHdpdGggbm8gbWF0Y2ggaW4geCB3aWxsIGhhdmUgTkEgdmFsdWVzIGluIHRoZSBuZXcgY29sdW1ucy4gSWYgdGhlcmUgYXJlIG11bHRpcGxlIG1hdGNoZXMgYmV0d2VlbiB4IGFuZCB5LCBhbGwgY29tYmluYXRpb25zIG9mIHRoZSBtYXRjaGVzIGFyZSByZXR1cm5lZC4KNC4gYGZ1bGxfam9pbigpYDogcmV0dXJuIDxzcGFuIGNsYXNzPSJoaWdobGlnaHQiPmFsbCByb3dzPC9zcGFuPiBhbmQgYWxsIGNvbHVtbnMgZnJvbSBib3RoIHggYW5kIHkuIFdoZXJlIHRoZXJlIGFyZSBub3QgbWF0Y2hpbmcgdmFsdWVzLCByZXR1cm5zIE5BIGZvciB0aGUgb25lIG1pc3NpbmcuCjUuIGBzZW1pX2pvaW4oKWA6IHJldHVybiBhbGwgcm93cyBmcm9tIHggd2hlcmUgdGhlcmUgYXJlIG1hdGNoaW5nIHZhbHVlcyBpbiB5LCBrZWVwaW5nIGp1c3QgY29sdW1ucyBmcm9tIHguIEEgc2VtaSBqb2luIGRpZmZlcnMgZnJvbSBhbiBpbm5lciBqb2luIGJlY2F1c2UgYW4gaW5uZXIgam9pbiB3aWxsIHJldHVybiBvbmUgcm93IG9mIHggZm9yIGVhY2ggbWF0Y2hpbmcgcm93IG9mIHksIHdoZXJlIGEgc2VtaSBqb2luIHdpbGwgbmV2ZXIgZHVwbGljYXRlIHJvd3Mgb2YgeC4KNi4gYGFudGlfam9pbigpYDogcmV0dXJuIGFsbCByb3dzIGZyb20geCB3aGVyZSB0aGVyZSBhcmUgbm90IG1hdGNoaW5nIHZhbHVlcyBpbiB5LCBrZWVwaW5nIGp1c3QgY29sdW1ucyBmcm9tIHgKCmBgYHtyfQpiYW5kX21lbWJlcnMKYGBgCgpgYGB7cn0KYmFuZF9pbnN0cnVtZW50cwpgYGAKYGBge3J9CmlubmVyX2pvaW4oYmFuZF9tZW1iZXJzLCBiYW5kX2luc3RydW1lbnRzKQpgYGAKCgoKYGBge3J9CmxlZnRfam9pbihiYW5kX21lbWJlcnMsIGJhbmRfaW5zdHJ1bWVudHMpCmBgYAoKCmBgYHtyfQpyaWdodF9qb2luKGJhbmRfbWVtYmVycywgYmFuZF9pbnN0cnVtZW50cykKYGBgCgpgYGB7cn0KZnVsbF9qb2luKGJhbmRfbWVtYmVycywgYmFuZF9pbnN0cnVtZW50cykKYGBgCgoKCmBgYHtyfQpzZW1pX2pvaW4oYmFuZF9tZW1iZXJzLCBiYW5kX2luc3RydW1lbnRzKQpgYGAKCgpgYGB7cn0KYW50aV9qb2luKGJhbmRfbWVtYmVycywgYmFuZF9pbnN0cnVtZW50cykKYGBgCgoKIyMjTGVmdCBKb2luClRoaXMgaXMgdGhlIG1vc3QgcG9wdWxhciB0eXBlIG9mIGpvaW4gKGFuYWxvZ291cyB0byBWbG9va3VwIGluIEV4Y2VsKS4KYGBge3J9CmxpYnJhcnkobnljZmxpZ2h0czEzKQoKZmxpZ2h0c1dpdGhXZWF0aGVyIDwtIGxlZnRfam9pbihmbGlnaHRzLCB3ZWF0aGVyLCBieSA9IGMoIm9yaWdpbiIsICJtb250aCIsICJkYXkiLCAiaG91ciIpKQpgYGAKCgpgYGB7cn0Kd2VhdGhlcgpgYGAKCgoKClteMV06IFRoZSBnYXBtaW5kZXIgcGFja2FnZSBpcyBhbiBleGNlcnB0IG9mIHRoZSBkYXRhIHRoYXQgZXhpc3RzIFtoZXJlXShodHRwczovL3d3dy5nYXBtaW5kZXIub3JnL2RhdGEvKS4gVGhpcyBkYXRhIHdhcyBjcmVhdGVkIGJ5IHRoZSBHYXBtaW5kZXIgRm91bmRhdGlvbiBsZWQgYnkgSGFucyBSb3NsaW5nLgpbXjJdOiBPdmVyLXBsb3R0aW5nIGhhcHBlbnMgd2hlbiBkYXRhIHBvaW50cyBhcmUgcGxvdHRlZCBvbiB0b3Agb2YgZWFjaCBvdGhlciBtdWx0aXBsZSB0aW1lcy4gSXQgbWFrZXMgY2hhcnRzIG1vcmUgY29uZnVzaW5nIGFuZCBkaWZmaWN1bHQgdG8gcmVhZC4gSXQgc2hvdWxkIGJlIGF2b2lkZWQgYXMgZmFyIGFzIHBvc3NpYmxlClteM106IFdlIGNvdWxkIGFsc28gZG8gdGhpcyBieSBmaWx0ZXJpbmcgb3V0IHRoZSBtb3N0IHJlY2VudCB5ZWFyIHRvIHN0dWR5IHRoZSByZWxhdGlvbnNoaXAuIEJvdGggYXJlIHZhbGlkIG9wdGlvbnMsIGhvd2V2ZXIgdGhlIGZvcm1lciBoYXMgdGhlIGJlbmVmaXQgb2YgY2FwdHVyaW5nIG1vcmUgaW5mb3JtYXRpb24gc2luY2UgaXQgdGFrZXMgaW50byBhY2NvdW50IHRoZSBlbnRpcmUgdGltZSBzZXJpZXMgb2YgZGF0YSB0aGF0IGlzIGF2YWlsYWJsZSB0byBjYWxjdWxhdGUgdGhlIG1lYW4gdmFsdWVzLCB3aGlsZSB0aGUgbGF0dGVyIGRpc3JlZ2FyZHMgYWxsIGJ1dCBvbmUgeWVhciBvZiB0aGUgZGF0YS4gSW4gdGhpcyBjYXNlIHRoaXMgc3VpdHMgb3VyIHB1cnBvc2VzIG9mIHdhbnRpbmcgdG8gc3R1ZHkgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIEdEUCBwZXIgY2FwaXRhIGFuZCBsaWZlIGV4cGVjdGFuY3kuClteNF06IFlvdSBjYW4gZG8gdGhpcyB1c2luZyB0aGUgZm9sbG93aW5nIGNvbW1hbmQgYGdncGxvdCguLCBhZXMoeCA9IGxvZyhtZWFuR0RQcGVyQ2FwLCBiYXNlID0gMTApLCB5ID0gbWVhbkxpZmVFeHApKWAgaW5zdGVhZCBvZiBgZ2dwbG90KC4sIGFlcyh4ID0gbWVhbkdEUHBlckNhcCwgeSA9IG1lYW5MaWZlRXhwKSlgLiBDYW4geW91IHRlbGwgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGlzIHBsb3QgYW5kIHRoZSBvbmUgdGhhdCB3YXMgbWFkZSB1c2luZyB0aGUgc2NhbGUgdHJhbnNmb3JtYXRpb24/ClteNV06IFRoaXM1IGFlc3RoZXRpYyBwYXJhbWV0ZXJzIGFyZSBub3QgYmVpbmcgbWFwcGVkIHRvIHRoZSBkYXRhLiBJbnN0ZWFkIHdlIGFyZSB1c2luZyB2ZWN0b3JzIHRoYXQgd2VyZSBjcmVhdGVkIG91dHNpZGUgdGhlIGN1cnJlbnQgZnVuY3Rpb24gY2FsbCB0byBzcGVjaWZ5IHRoZSB2YWx1ZXMgZm9yIHRoZXNlIHZlY3RvcnMuCg==